#include <string.h>
#include <iostream>
#include <errno.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <stdio.h>
#include <signal.h>

#include "ChatClient.hpp"

// Define Static Constants
const int ChatClient::RECV_BUF_ARRAY_SIZE      = 512; // Max buffer size
const int ChatClient::SEND_BUF_ARRAY_SIZE      = 512; // Max buffer size
const int ChatClient::CLIENT_ONLINE            = 1;
const int ChatClient::CLIENT_OFFLINE           = 0;
const int ChatClient::CLIENT_MSGS              = -1;
const std::string ChatClient::PROMPT           = "$ >>> ";

/** Class Constructors and Destructors **/

ChatClient::ChatClient()
{

}

// Server Constructor
ChatClient::ChatClient(int port) : m_is_server(true)
{
    // Set this program's information
    m_client_ip_port.sin_family         = AF_INET; // IPv4
    m_client_ip_port.sin_port           = htons(u_short(port));
    m_client_ip_port.sin_addr.s_addr    = htonl(INADDR_ANY);

    m_program_socket_id = socket(PF_INET, SOCK_DGRAM, 0);
    
    // Zero out all struct
    memset((void*)&m_remote_ip_port, 0, sizeof(m_remote_ip_port));

    // Zero out the recieve and send buffers, remember this is a pointer
    m_socket_recv_buffer = new char[RECV_BUF_ARRAY_SIZE];
    m_socket_send_buffer = new char[SEND_BUF_ARRAY_SIZE];
    memset((void*) m_socket_recv_buffer, 0, RECV_BUF_ARRAY_SIZE);
    memset((void*) m_socket_send_buffer, 0, SEND_BUF_ARRAY_SIZE);

    std::cout << PROMPT << "Buffer Address being used: " << int(m_socket_recv_buffer) << std::endl;

    m_is_running = false;

}

// Client Constructor
ChatClient::ChatClient
(
    const char * p_nickname, 
    const char * p_dotted_ip, 
    int server_port, 
    int client_port
) : m_nickname(p_nickname), m_server_port(server_port), m_client_port(client_port), m_is_server(false)
{ 
    // Set our designated server information
    m_server_ip_port.sin_family         = AF_INET; // IPv4
    m_server_ip_port.sin_port           = htons(u_short(m_server_port));
    inet_aton(p_dotted_ip, &(m_server_ip_port.sin_addr));

    // Set this program's information
    m_client_ip_port.sin_family         = AF_INET; // IPv4
    m_client_ip_port.sin_port           = htons(u_short(m_client_port));
    m_client_ip_port.sin_addr.s_addr    = htonl(INADDR_ANY);

    m_program_socket_id = socket(PF_INET, SOCK_DGRAM, 0);
    
    // Zero out all struct
    memset((void*)&m_remote_ip_port, 0, sizeof(m_remote_ip_port));

    // Zero out the recieve buffer, remember this is a pointer
    m_socket_recv_buffer = new char[RECV_BUF_ARRAY_SIZE];
    m_socket_send_buffer = new char[SEND_BUF_ARRAY_SIZE];
    memset((void*) m_socket_recv_buffer, 0, RECV_BUF_ARRAY_SIZE);
    memset((void*) m_socket_send_buffer, 0, SEND_BUF_ARRAY_SIZE);

    std::cout << PROMPT << "Buffer Address being used: " << int(m_socket_recv_buffer) << std::endl;

    m_is_running = false;
}

// Destructor
ChatClient::~ChatClient()
{
    delete[] m_socket_recv_buffer;
    delete[] m_socket_send_buffer;
}



/** Mode Agnostic Functions **/
// This function is responsible for attempting to bind the socket to the specified port
// It will try at most the number of specified times to attempt to bind.
// Returns 0 on success and -1 on failure
int ChatClient::start(int attempts)
{
    // Attempts to bind the socket a maximum of attempts times before giving up.

    bool success = false;
    int num_attempts = 0;
    int bound = -1;
    int bound_errorno = 0;
    while(num_attempts < attempts && !success)
    {
        bound = bind(m_program_socket_id,
                    (struct sockaddr *) &m_client_ip_port, 
                     sizeof(m_client_ip_port));
        bound_errorno = errno; 
        success = (bound == 0);
        num_attempts++;
    }


    m_is_running = success;
   
    if(!success)
    {
       // Recieve stuff, but an error obviously occured
       char * myerrorstr = strerror(bound_errorno);
       std::cerr << std::string(myerrorstr) << std::endl;

    }
    return bound;
}

void ChatClient::stop()
{
    close(m_program_socket_id);
    m_is_running = false;
}

// This will be a blocking function that waits for data to come into the listening port.
int ChatClient::listen()
{
    // Clear recieving info structs
    memset((void * ) &m_remote_ip_port, 0, sizeof(m_remote_ip_port));
    memset((void * ) m_socket_recv_buffer, 0, sizeof(char)*RECV_BUF_ARRAY_SIZE);
   
    unsigned int fromwho_size = sizeof(m_remote_ip_port);
    // This below is a blocking function, which won't return til we get a packet
    m_socket_recv_buffer_size = 
    recvfrom
    (
        m_program_socket_id,
        (void *) m_socket_recv_buffer,
        sizeof(char)*RECV_BUF_ARRAY_SIZE,
        0,
        (struct sockaddr *) &m_remote_ip_port,
        &fromwho_size
    );

    m_recv_errorno = errno; 
    if(m_socket_recv_buffer_size == -1)
    {
        // Recieve stuff, but an error obviously occured
        char * myerrorstr = strerror(m_recv_errorno);
        std::cerr << std::string(myerrorstr) << std::endl;
    }

    return m_socket_recv_buffer_size;
}

void ChatClient::parse_data()
{
    if(m_is_server)
    {
        parse_data_server();
    }
    else
    {
        parse_data_client();
    }

}

void ChatClient::print_socket_info()
{
    print_socket_info(m_client_ip_port);
}

void ChatClient::print_socket_info(sockaddr_in & p_sock)
{
    // Need to print the IP and port info of the socket that we are using
    // Remember that we are storing the information in network byte order
    std::cout << PROMPT << "port: " << ntohs(p_sock.sin_port) << std::endl;
    std::cout << PROMPT << "inet addr: " << inet_ntoa(p_sock.sin_addr) << std::endl;
}

bool ChatClient::isRunning()
{
    return m_is_running;
}

bool ChatClient::validate_recv_packet_header()
{
    bool checkType = true;
    checkType = checkType && (m_socket_recv_buffer[0] == 'U');
    checkType = checkType && (m_socket_recv_buffer[1] == 'D');
    checkType = checkType && (m_socket_recv_buffer[2] == 'P');
    checkType = checkType && (m_socket_recv_buffer[3] == 'C');
    checkType = checkType && (m_socket_recv_buffer[4] == 'H');
    checkType = checkType && (m_socket_recv_buffer[5] == 'A');
    checkType = checkType && (m_socket_recv_buffer[6] == 'T');

    return checkType;

}

void ChatClient::construct_packet_header(char packet_type)
{
    m_socket_send_buffer[0] = 'U';
    m_socket_send_buffer[1] = 'D';
    m_socket_send_buffer[2] = 'P';
    m_socket_send_buffer[3] = 'C';
    m_socket_send_buffer[4] = 'H';
    m_socket_send_buffer[5] = 'A';
    m_socket_send_buffer[6] = 'T';
    m_socket_send_buffer[7] = packet_type; // Indicates the packet type
}

// Updates/adds a single entry to the client table
void ChatClient::update_client_table(std::string & nickname, int client_status, sockaddr_in & client_ip_port)
{
    // Attempt to insert new record into the client table
    std::pair<client_table_type::iterator, bool > result
        = c_client_table.insert
        ( 
            std::pair<std::string, std::pair<int, sockaddr_in> >
            (
                nickname, std::pair<int, sockaddr_in>
                (
                    client_status,
                    client_ip_port
                )
            )
        );
    
    // Checks the result of the insert
    if(result.second) // True
    {
        std::cout << PROMPT << "Added a new client to the database!" << std::endl;
    }
    else // False
    {
        // Client table needs an update. Dereference the iterator, and update the values
        result.first->second.first = client_status;
        result.first->second.second.sin_addr = client_ip_port.sin_addr;
        result.first->second.second.sin_port = client_ip_port.sin_port;

        std::cout << PROMPT << "Updated client database information" << std::endl;
    }

}

// Prints the client table to STDOUT
void ChatClient::print_client_table()
{
    if(c_client_table.size() < 1)
    {
        std::cout << PROMPT << "There are no clients in the table." << std::endl;
    }
    else
    {
        std::cout << PROMPT << "Nickname, IP, Port" << std::endl;
        for(client_table_type::iterator it = c_client_table.begin(); it != c_client_table.end(); it++)
        {
            std::cout << PROMPT << it->first << "," 
                      << ((it->second.first == CLIENT_ONLINE) ? "ONLINE" : "OFFLINE") << ","
                      << inet_ntoa(it->second.second.sin_addr) << ","
                      << ntohs(it->second.second.sin_port) << std::endl;
        }
    }
}

// Select Blocking for a certain number of milliseconds
// If wait time is larger than 0, then will only listen to the socket
// If wait time is 0, then listen to both the socket and stdin
int ChatClient::demultiplex(int wait_in_milliseconds)
{

    int nfds = -1;
    int result = -1;
    // Set up the socket fd

    int stdin_fd = fileno(stdin);
    
    fd_set fd_list;
    // Keep polling the file descriptors until a response is recieved
    do
    {
        FD_ZERO(&fd_list); // Zero out the file descriptors first
        FD_SET(m_program_socket_id, &fd_list);  // Set to listen to the socket
        FD_SET(stdin_fd, &fd_list); // Also listen to STDIN for the user's input
        nfds = ((stdin_fd > m_program_socket_id) ? stdin_fd : m_program_socket_id) + 1;


        sigset_t ignored;
        sigemptyset(&ignored);
        sigaddset(&ignored, SIGINT);
        
        timespec timeout;
        timeout.tv_sec = 0L;
        timeout.tv_nsec = 0L;
       
        // Set up the time struct
        if(wait_in_milliseconds > 0)
        {
            // Set the timeout
            int num_seconds = wait_in_milliseconds / 1000;
            int remainder   = wait_in_milliseconds - 1000*num_seconds;
            // Take number of milliseconds, and convert to nanoseconds

            long nano_s = long(remainder) * 1000000L;
            timeout.tv_sec  = long(num_seconds);
            timeout.tv_nsec = long(nano_s);

        }

        result = pselect(nfds, &fd_list, NULL, NULL, &timeout, &ignored); // Block for at most the specified number of milliseconds before continuing

    } while(result == 0);

    if(result == -1)
    {
        std::cout << PROMPT << "An error occured while polling, exiting." << std::endl;
        result = -1;
    }

    // There is something to read from the one of the file descriptors

    bool check_stdin    = FD_ISSET(stdin_fd, &fd_list);
    bool check_socket   = FD_ISSET(m_program_socket_id, &fd_list);
    
    if(check_stdin && check_socket)
    {
        result = 2;
    }
    else if(check_stdin && !check_socket)
    {
        result = 0;
    }
    else if(!check_stdin && check_socket)
    {
        result = 1;
    }
    else
    {
        result = -1;
    }
   
    return result;

}


void ChatClient::process_stdin()
{
    // send command -- leading spaces allowed, followed by send, following by atleast 1 space, followed by client name, followed by at least 1 space, followed by the message
    boost::regex send_msg_regEx("^\\s*send\\s+(\\w+)\\s+(.+)$");
    boost::regex dereg_msg_regEx("^\\s*dereg\\s+(\\w+)\\s*$");
    boost::regex reg_msg_regEx("^\\s*reg\\s+(\\w+)\\s*$");
    
    std::string line;
    // Read a single line from STDIN
    std::getline(std::cin, line);

    // parse STDIN line 
    // string, match_results, regex
    boost::smatch line_components;
    if(boost::regex_match(line, line_components, send_msg_regEx) && m_is_registered)
    {
        // If stdin matches a send command, then handle send command process 
        std::string to_nickname = line_components[1];
        std::string to_message  = line_components[2];
        
        handle_send_message(to_nickname, to_message, false);

    }
    else if(boost::regex_match(line, line_components, dereg_msg_regEx) && m_is_registered)
    {
        // Check to see if the entered nickname matches this client's nickname.
        std::string my_nickname = line_components[1];

        if(m_nickname.compare(my_nickname) != 0)
        {
            std::cout << PROMPT << "You can only de-register yourself." << std::endl;
        }
        else
        {
            handle_send_dereg_request();
        }
    }
    else if(boost::regex_match(line, line_components, reg_msg_regEx) && !m_is_registered)
    {
        std::string my_nickname = line_components[1];
        m_nickname = my_nickname;
        
        handle_register_client();
    }



}

int ChatClient::handle_send_dereg_request()
{
    construct_packet_header('D');
    int idx = 8;
    strcpy(&(m_socket_send_buffer[idx]), m_nickname.c_str());
    idx = idx + m_nickname.size() + 1;
    bool recv_ACK = false;
    int count  = 0;
    do
    {
        // Send the request to the server
        sendto(m_program_socket_id, 
               (void * ) m_socket_send_buffer,
               idx,
               0,
               (sockaddr * ) &m_server_ip_port,
               sizeof(sockaddr_in));
        // Wait for ACK from the server 
        recv_ACK = listen_for_ack(500, m_server_ip_port);
        count++;
    } while(!recv_ACK && count < 5);

    if(!recv_ACK)
    {
        std::cout << PROMPT << "Server not responding." << std::endl;
        std::cout << PROMPT << "Exiting." << std::endl;
        exit(0);
    }
    else
    {
        std::cout << PROMPT << "[You are offline. Bye!]" << std::endl;
        m_is_registered = false;
    }

}


int ChatClient::handle_send_message(std::string & nickname, std::string & msg, bool need_timestamp)
{
    int return_result = -1;
    // Search our client table for the nickname
    client_table_type::iterator client_it = c_client_table.find(nickname); // Check if the nickname is in the table
    if(client_it == c_client_table.end())
    {
        // Client does not exist in the table
        // Display message about non-existance
        return_result = -1;
    }
    else
    {
        // Client exists in the table
        // Check if it is online or not
        if(client_it->second.first == CLIENT_ONLINE)
        {
            // Send message to client -- first construct the message
            construct_packet_header('M'); // Type of message
            int idx = 8;
            strcpy(&(m_socket_send_buffer[idx]), m_nickname.c_str());
            idx = idx + m_nickname.size() + 1;
            strcpy(&(m_socket_send_buffer[idx]), msg.c_str());
            idx = idx + msg.size() + 1;

            sendto(m_program_socket_id, 
                   (void * ) m_socket_send_buffer,
                   idx,
                   0,
                   (sockaddr * ) &(client_it->second.second),
                   sizeof(sockaddr_in));
            
            std::cout << PROMPT << "Listening for ACK" << std::endl;

            bool gotACK = listen_for_ack(500, client_it->second.second);

            std::cout << PROMPT << "stopped listening for ACK" << std::endl;
            if(gotACK)
            {
                std::cout << PROMPT << "[Message recieved by " << client_it->first << "]" << std::endl;
            }
            else
            {
                //  Message not recieved, so set the client to be offline
                client_it->second.first = CLIENT_OFFLINE;
            }

        }
        
        if(client_it->second.first == CLIENT_OFFLINE)// Client is offline
        {
            std::cout << PROMPT << "[No ACK recieved from " << client_it->first << ", sending message to server]" << std::endl;
            // Send the message to the server
            // UDPCHATM[TO CLIENT]\0[FROM CLIENT]\0[MESSAGE]\0
            construct_packet_header('M'); // Type of message
            int idx = 8;
            strcpy(&(m_socket_send_buffer[idx]),   nickname.c_str()); // To Field
            idx = idx + nickname.size() + 1;
            strcpy(&(m_socket_send_buffer[idx]), m_nickname.c_str()); // From field
            idx = idx + m_nickname.size() + 1;
            strcpy(&(m_socket_send_buffer[idx]), msg.c_str()); // Message field
            idx = idx + msg.size() + 1;
            
            // Listen for ACK from server
            bool recv_ACK = false;
            int count  = 0;
            do
            {
                // Send the request to the server
                sendto(m_program_socket_id, 
                       (void * ) m_socket_send_buffer,
                       idx,
                       0,
                       (sockaddr * ) &m_server_ip_port,
                       sizeof(sockaddr_in));
                // Wait for ACK from the server 
                recv_ACK = listen_for_ack(500, m_server_ip_port);
                count++;
            } while(!recv_ACK && count < 5);

            if(!recv_ACK)
            {
                std::cout << PROMPT << "Server not responding." << std::endl;
                std::cout << PROMPT << "Exiting." << std::endl;
                exit(0);
            }
            else
            {
                std::cout << PROMPT << "[Message sent to server]" << std::endl;
            }
        }
    }
}

// Select Blocking for a certain number of milliseconds
// If wait time is larger than 0, then will only listen to the socket
// If wait time is 0, then listen to both the socket and stdin
int ChatClient::listen_for_reg_response(int wait_in_milliseconds, sockaddr_in & src)
{

    int nfds = -1;
    int result = -1;
    long nano_seconds = long(wait_in_milliseconds)*1000000L;
    
    // Set up the socket fd
    
    fd_set fd_list;
    
    // Keep polling the file descriptors until a response is recieved
    FD_ZERO(&fd_list); // Zero out the file descriptors first
    FD_SET(m_program_socket_id, &fd_list);  // Set to listen to the socket
    nfds = m_program_socket_id + 1;

    sigset_t ignored;
    sigemptyset(&ignored);
    sigaddset(&ignored, SIGINT);
    
    // Listen using 1 ms resolution to allow blocking for 1 ms.
    // Tried 0 before, but would only ever see ACK after the 500 ms had expired.
    // Probably has to do with not letting the other process execute until the loop was finished,
    // even though the other client would recieve the message and send an ACK
    // I was running this on a single VM.
    // Adding a 1 ms blocking time solve the problem
    timespec timeout;
    timeout.tv_sec = 0L;
    timeout.tv_nsec = 1000000L;

    timespec timestart;
    timestart.tv_sec = 0L;
    timestart.tv_nsec = 0L;
    
    timespec time_stop;
    time_stop.tv_sec = 0L;
    time_stop.tv_nsec = 0L;

    clock_gettime(CLOCK_MONOTONIC, &timestart);
    time_stop.tv_sec = timestart.tv_sec;
    time_stop.tv_nsec = timestart.tv_sec;

    long elapsed_time = 0;
    bool noACK = true;
    int count = 0;
    while(elapsed_time < nano_seconds && noACK)
    {
        result = pselect(nfds, &fd_list, NULL, NULL, &timeout, &ignored); // Block for at most the specified number of milliseconds before continuing
        
        if(result == -1)
        {
            std::cout << PROMPT << "An error occured while polling, exiting." << std::endl;
            return false;
        }
        
        if(result == 1)
        {
            // Check the socket for valid data.
            if(FD_ISSET(m_program_socket_id, &fd_list))
            {
                int buffer_size = listen(); // Read the socket
                // Read the data from the socket
                bool valid_header = validate_recv_packet_header();

                noACK = !(valid_header &&   m_socket_recv_buffer[7] == 'Q' &&
                                            m_remote_ip_port.sin_addr.s_addr == src.sin_addr.s_addr &&
                                            m_remote_ip_port.sin_port == src.sin_port);
                
                if(noACK){ parse_data();} // It's not an ACK, maybe it's a broadcast or client message
            }
        }
        
        clock_gettime(CLOCK_MONOTONIC, &time_stop);
        elapsed_time = (time_stop.tv_sec == timestart.tv_sec) ? (time_stop.tv_nsec - timestart.tv_nsec) : (nano_seconds + 1);
        count++;
    }
    std::cout << PROMPT << "Listen for an REG response " << count << " times." << std::endl;
    int return_val = -1;
    if(!noACK) // :
    {
        if(m_socket_recv_buffer[8] == 'Y') return_val = 1; // Registration request was successful
        if(m_socket_recv_buffer[8] == 'N') return_val = 0; // Registration request was unsuccessful
    }
    return return_val;
}
// Select Blocking for a certain number of milliseconds
// If wait time is larger than 0, then will only listen to the socket
// If wait time is 0, then listen to both the socket and stdin
bool ChatClient::listen_for_ack(int wait_in_milliseconds, sockaddr_in & src)
{

    int nfds = -1;
    int result = -1;
    long nano_seconds = long(wait_in_milliseconds)*1000000L;
    
    // Set up the socket fd
    
    fd_set fd_list;
    
    // Keep polling the file descriptors until a response is recieved
    FD_ZERO(&fd_list); // Zero out the file descriptors first
    FD_SET(m_program_socket_id, &fd_list);  // Set to listen to the socket
    nfds = m_program_socket_id + 1;

    sigset_t ignored;
    sigemptyset(&ignored);
    sigaddset(&ignored, SIGINT);
    
    // Listen using 1 ms resolution to allow blocking for 1 ms.
    // Tried 0 before, but would only ever see ACK after the 500 ms had expired.
    // Probably has to do with not letting the other process execute until the loop was finished,
    // even though the other client would recieve the message and send an ACK
    // I was running this on a single VM.
    // Adding a 1 ms blocking time solve the problem
    timespec timeout;
    timeout.tv_sec = 0L;
    timeout.tv_nsec = 1000000L;

    timespec timestart;
    timestart.tv_sec = 0L;
    timestart.tv_nsec = 0L;
    
    timespec time_stop;
    time_stop.tv_sec = 0L;
    time_stop.tv_nsec = 0L;

    clock_gettime(CLOCK_MONOTONIC, &timestart);
    time_stop.tv_sec = timestart.tv_sec;
    time_stop.tv_nsec = timestart.tv_sec;

    long elapsed_time = 0;
    bool noACK = true;
    int count = 0;
    while(elapsed_time < nano_seconds && noACK)
    {
        result = pselect(nfds, &fd_list, NULL, NULL, &timeout, &ignored); // Block for at most the specified number of milliseconds before continuing
        
        if(result == -1)
        {
            std::cout << PROMPT << "An error occured while polling, exiting." << std::endl;
            return false;
        }
        
        if(result == 1)
        {
            // Check the socket for valid data.
            if(FD_ISSET(m_program_socket_id, &fd_list))
            {
                int buffer_size = listen(); // Read the socket
                // Read the data from the socket
                bool valid_header = validate_recv_packet_header();

                noACK = !(valid_header &&   m_socket_recv_buffer[7] == 'A' &&
                                            m_remote_ip_port.sin_addr.s_addr == src.sin_addr.s_addr &&
                                            m_remote_ip_port.sin_port == src.sin_port);
                
                std::cout << PROMPT << "Listening for ACK from: "<< std::endl;
                std::cout << PROMPT << "Recieved packet from: " << std::endl;
                if(noACK){ parse_data();} // It's not an ACK, maybe it's a broadcast or client message
            }
        }
        
        clock_gettime(CLOCK_MONOTONIC, &time_stop);
        elapsed_time = (time_stop.tv_sec == timestart.tv_sec) ? (time_stop.tv_nsec - timestart.tv_nsec) : (nano_seconds + 1);
        count++;
    }
    std::cout << PROMPT << "Listen for an ACK " << count << " times." << std::endl;
    return !noACK;
}

void ChatClient::send_ack(sockaddr_in & dst)
{
    construct_packet_header('A'); // For ACK
    sendto(m_program_socket_id, 
            (void * ) m_socket_send_buffer,
            8,
            0,
            (sockaddr * ) &dst,
            sizeof(sockaddr_in));
}

void ChatClient::send_confirmation(char c, sockaddr_in & dst)
{
    construct_packet_header('Q'); // For confirmation
    m_socket_send_buffer[8] = c;
    sendto(m_program_socket_id, 
            (void * ) m_socket_send_buffer,
            9,
            0,
            (sockaddr * ) &dst,
            sizeof(sockaddr_in));
}


/*****************************
 ** MODE : SERVER FUNCTIONS **
 *****************************/

// Registers the client with the server.
// Input: Client IP and Port Number (from struct)
// Input: Client Nickname
void ChatClient::register_client(std::string nickname, sockaddr_in & addrport)
{
    // Check to see if the client is already registered. If it is, then don't allow the registration to occur, and send a not allowed packet back


    update_client_table(nickname, CLIENT_ONLINE, addrport);
}

//void ChatClient::deregister_client();

// Populates client status into the packet
// Returns amount of buffer currently populated (header + total data)
int ChatClient::populate_client_table_packet_data()
{
    uint32_t num_clients = c_client_table.size(); // Number of registered clients, converted to network byte order
    int ci = 8; // Current index
    std::cout << PROMPT << "Setting number of clients in table, that number is " << num_clients << std::endl;
    uint32_t num_clients_for_packet = htonl(num_clients);
    memcpy((void*)&(m_socket_send_buffer[ci]), (const void *)&(num_clients_for_packet), sizeof(num_clients_for_packet)); ci = ci + sizeof(num_clients_for_packet);
    std::cout << PROMPT << "Successful, the network byte order value is " << num_clients_for_packet << std::endl;

    // Populate info for each client
    for(client_table_type::iterator it = c_client_table.begin(); it != c_client_table.end(); it++)
    {
        // Client IP and Port, these are already stored in network byte order, so no conversion is necessary
        memcpy((void*)&(m_socket_send_buffer[ci]), (const void *)&( it->second.second.sin_addr ), sizeof(it->second.second.sin_addr)); ci = ci + sizeof(it->second.second.sin_addr);
        memcpy((void*)&(m_socket_send_buffer[ci]), (const void *)&( it->second.second.sin_port ), sizeof(it->second.second.sin_port)); ci = ci + sizeof(it->second.second.sin_port);
        char online_status = 0;
        if(it->second.first == CLIENT_ONLINE )
        {
            online_status = '1'; 
        } else { // The client is offline
            online_status = '0';
        }
        // Client Online Status
        m_socket_send_buffer[ci] = online_status; ci = ci+1;
        // Client Nickname
        strcpy( &(m_socket_send_buffer[ci]), it->first.c_str()); // We just added nickname_length + 1 (for the null character)
        ci = ci + it->first.size() + 1; // String length plus \0 character
    }
    return ci;
}

// This function broadcasts client information to all the currently known clients
void ChatClient::broadcast_client_status()
{
    // For each of the clients currently online, broadcast the whole client status table to each of the online clients.
    //
    // Construct the Table to be sent to each client
    
    // Use the 8th byte as 'B' to indicate Client Table Status Broadcast
    // 'UDPCHATB' indicates Client Table Status Broadcast
    // The next field will be the number of clients in the table, which will be an UINT 32, converted to network order
    // The next field will be the IP address of the client  (c_addrport[i].sin_addr) // This is already stored in network order
    // The next field will be the Port Number of the client (c_addrport[i].sin_port) // This is already stored in network order
    // The next field will be the status field, a 1 character field, with '0' as OFFLINE and '1' as ONLINE
    // The next field will be a variable length field, the client's nickname, terminated by the null character '\0'
    // The we repeat the above.
    construct_packet_header('B'); // Indicates a client table broadcast packet type
    int consumed_buffer_size = populate_client_table_packet_data();
    sockaddr_in * client_ip_port;
    
    for(client_table_type::iterator it = c_client_table.begin(); it != c_client_table.end(); it++)
    {
        if(it->second.first == CLIENT_ONLINE)
        {
            // Send table to client
            client_ip_port = &(it->second.second);
            sendto(m_program_socket_id, 
                   (void * ) m_socket_send_buffer,
                   consumed_buffer_size,
                   0,
                   (sockaddr * ) client_ip_port,
                   sizeof(sockaddr_in));
        }
    }
}

void ChatClient::parse_data_server()
{
    // Here we parse the recv buffer to see what type of data was recieved.
    // We expet the first few bytes to explicitly identify the application:
    // UDPCHAT should be the first 7 bytes.
    //
    // The 8th byte should indicate the packet type
    // 1) registration paket
    // 2) de-registration packet
    // 3) off-line chat packet
    //
    // let 1 = registration packet
    // let 2 = de-registration packet
    // let 3 = off-line chat request
    //
    // Registration packet 
    // -- the IP Address and Port number were handled by the other layer,
    // so they aren't needed here.
    // After the 8th byte, will be the nickname until the end of the datagram

    if(m_socket_recv_buffer_size <= 7)
    {
        // Discard this buffer, since it has been corrupted--it's too small.
        std::cout << PROMPT << "The server got something really tiny that isn't for this chat program." << std::endl;
    }
    else
    {
        bool checkType = validate_recv_packet_header();
        if(checkType)
        {
            // Correct header found.
            char type = m_socket_recv_buffer[7];
            if('A'==type)
            {
                // Recieved an ACK
                std::cout << PROMPT << "Recieved an ACK" << std::endl;
            }
            else if('R'== type)
            {
                // If a nickname, need to make sure that the last element is indeed the '\0' character
                
                if(m_socket_recv_buffer[m_socket_recv_buffer_size-1] == '\0')
                {
                    std::string s(&(m_socket_recv_buffer[8])); // Convert C-string to string
                    send_ack(m_remote_ip_port);
                    // Registration Request
                    // Check to see if the client is already registered
                    client_table_type::iterator it = c_client_table.find(s);
                    if(it == c_client_table.end())
                    {
                        send_confirmation('Y', m_remote_ip_port);
                        register_client(s, m_remote_ip_port);
                        broadcast_client_status();
                    }
                    else
                    {
                        if(it->second.first == CLIENT_OFFLINE)
                        {
                            send_confirmation('Y', m_remote_ip_port);
                            register_client(s, m_remote_ip_port);
                            // Send offline messages to the client that just logged in
                            if(!offline_message_table[s].empty())
                            {
                                construct_packet_header('Z');
                                sendto(m_program_socket_id, 
                                        (void * ) m_socket_send_buffer,
                                        8,
                                        0,
                                        (sockaddr * ) &m_remote_ip_port,
                                        sizeof(sockaddr_in));
                                bool responding = true;
                                while(!offline_message_table[s].empty() && responding)
                                {
                                    std::list<offline_message_type>::iterator it_msg = offline_message_table[s].begin();
                                    
                                    construct_packet_header('O');
                                    int idx = 8;
                                    strcpy(&(m_socket_send_buffer[idx]), it_msg->first.c_str());
                                    idx = idx + it_msg->first.size() + 1;
                                    strcpy(&(m_socket_send_buffer[idx]), it_msg->second.c_str());
                                    idx = idx + it_msg->second.size() + 1;
                                   
                                    bool recv_ACK = false;
                                    int count = 0;
                                    do
                                    {
                                        sendto(m_program_socket_id, 
                                            (void * ) m_socket_send_buffer,
                                            idx,
                                            0,
                                            (sockaddr * ) &m_remote_ip_port,
                                            sizeof(sockaddr_in));
                                        recv_ACK = listen_for_ack(500, m_remote_ip_port);
                                        count++;
                                    } while(!recv_ACK && count < 5);
                                    
                                    responding = recv_ACK;
                                    if(recv_ACK)
                                    {
                                        offline_message_table[s].pop_front();
                                    }
                                }
                            }
                            broadcast_client_status();
                        }
                        else
                        {
                            send_confirmation('N', m_remote_ip_port);
                        }
                    }
                }
            }
            else if ('D' == type)
            {
                //
                std::string recv_nick(&(m_socket_recv_buffer[8]));
                client_table_type::iterator it = c_client_table.find(recv_nick);
                // If the client is valid and found in the table, then send an ACK

                if(it != c_client_table.end())
                {
                    send_ack(it->second.second);
                    it->second.first = CLIENT_OFFLINE;
                    broadcast_client_status(); 
                }
            }
            else if ('M' == type) // Offline Chat Request
            {
                // UDPCHATM[TO CLIENT]\0[FROM_CLIENT]\0[message]\0
                int idx = 8;
                std::string to_client(&(m_socket_recv_buffer[idx])); // Reads the to client name
                idx = idx + to_client.size() + 1;
                std::string from_client(&(m_socket_recv_buffer[idx])); // Reads the from client name
                idx = idx + from_client.size() + 1;
                std::string message(&(m_socket_recv_buffer[idx]));

                sockaddr_in from_ip_port;
                from_ip_port.sin_addr = m_remote_ip_port.sin_addr;
                from_ip_port.sin_port = m_remote_ip_port.sin_port;
                
                // Send ACK to FROM client
                send_ack(m_remote_ip_port); // Send ACK back to the FROM client

                std::cout << PROMPT << "Recieved off-line message request: " << std::endl;
                std::cout << PROMPT << "FROM: " << from_client << std::endl;
                std::cout << PROMPT << "TO: " << to_client << std::endl;
                std::cout << PROMPT << "MSG: " << message << std::endl;

                // Now, we need to check if the client is online or offline.
                // Is the TO client really offline? Let's find out.
                // Send an Poke REquest to the client

                client_table_type::iterator it = c_client_table.find(to_client);

                if(it != c_client_table.end()) // Valid client
                {
                    if(it->second.first == CLIENT_ONLINE)// Supposedly it is online, but FROM thinks it's offline. We need to confirm what FROM observed
                    {
                        // Our table says it's online.
                        construct_packet_header('P'); // We send a poke request to the TO client to check if it is active
                        sendto(m_program_socket_id, 
                               (void * ) m_socket_send_buffer,
                               8,
                               0,
                               (sockaddr * ) &(it->second.second),
                               sizeof(sockaddr_in));

                        // Listen for an ACK
                        bool gotACK = listen_for_ack(500, it->second.second);
                        
                        if(gotACK)
                        {
                            // The TO Client is accessible.
                            
                            // Send ERROR message to the FROM client
                            construct_packet_header('E');
                            idx = 8;
                            // Name of the TO client
                            strcpy(&(m_socket_send_buffer[idx]), to_client.c_str());
                            idx = idx + to_client.size() + 1;
                            sendto(m_program_socket_id, 
                                   (void * ) m_socket_send_buffer,
                                   idx,
                                   0,
                                   (sockaddr * ) &from_ip_port,
                                   sizeof(sockaddr_in));

                            // Send copy of the broadcast table to the FROM client
                            int size = populate_client_table_packet_data();
                            
                            sendto(m_program_socket_id, 
                                   (void * ) m_socket_send_buffer,
                                   size,
                                   0,
                                   (sockaddr * ) &from_ip_port,
                                   sizeof(sockaddr_in));
                            
                            // Deliver the message to the TO client on behalf of the FROM client
                            
                            construct_packet_header('M');
                            idx = 8;
                            // Name of the TO client
                            strcpy(&(m_socket_send_buffer[idx]), from_client.c_str());
                            idx = idx + to_client.size() + 1;
                            strcpy(&(m_socket_send_buffer[idx]), message.c_str());
                            idx = idx + message.size() + 1;
                            sendto(m_program_socket_id, 
                                   (void * ) m_socket_send_buffer,
                                   idx,
                                   0,
                                   (sockaddr * ) &(it->second.second),
                                   sizeof(sockaddr_in));
                        }
                        else
                        {
                            // TO client is offline, so update the client table.
                            it->second.first = CLIENT_OFFLINE;
                            broadcast_client_status();
                        }
                    }
                    // Do we record it as offline?
                    if(it->second.first == CLIENT_OFFLINE)
                    {
                        // Save chat to be sent later
                        std::cout << PROMPT << "SAVING OFFLINE CHAT" << std::endl;
                        // Get the time
                        time_t rawtime;
                        time(&rawtime);
                        char * char_timestamp = ctime(&rawtime);
                        std::string timestamp(char_timestamp);
                        // Change the newline character to a space
                        int length = timestamp.size();
                        timestamp[length-1] = ' ';
                        std::string full_message("[");
                        full_message.append(timestamp);
                        full_message.append("]: ");
                        full_message.append(message);
                        
                        // Create a temporary offline message type:
                        offline_message_type o_msg(from_client, full_message);
                        offline_message_table[to_client].push_back(o_msg);
                        std::cout << PROMPT << "Saved message for: " << to_client << std::endl;
                        std::cout << PROMPT << full_message << std::endl;
                    }
                    
                }
            }
            else
            {
                // Request Type not Recognized
            }
        }
        else
        {
            // Invalid packet
            std::cout << PROMPT << "The server recieved an invalid packet!" << std::endl;
        }
    }


}

/*****************************
 ** MODE : CLIENT FUNCTIONS **
 *****************************/
void ChatClient::handle_register_client()
{
    construct_packet_header('R');
    strcpy( &(m_socket_send_buffer[8]), m_nickname.c_str()); // We just added nickname_length + 1 (for the null character)

    // If the nickname is N characters long, then the \0 character will be located at index 8+N
    // We have the requirement that 8+N < SEND_BUF_ARRAY_SIZE
    // This means the maximum length of the nickname can only be N = SEND_BUF_ARRAY_SIZE-8-1

    bool recv_ACK = false;
    int count  = 0;
    do
    {
        // Send the request to the server
        sendto(m_program_socket_id, 
               (void * ) m_socket_send_buffer,
               9+m_nickname.size(),
               0,
               (sockaddr * )&(m_server_ip_port),
               sizeof(m_server_ip_port));

        // Wait for ACK from the server 
        recv_ACK = listen_for_ack(500, m_server_ip_port);
        count++;
    } while(!recv_ACK && count < 5);
    
    if(recv_ACK) // Server is online.
    {
        int success = listen_for_reg_response(500, m_server_ip_port);
        if(success == -1)
        {
            std::cout << PROMPT << "Exiting, server offline" << std::endl;
            m_is_registered = false;
            exit(0);
        }
        if(success == 1)
        {
            std::cout << PROMPT << "You registered successfully." << std::endl;
            m_is_registered = true;
        }
        if(success == 0)
        {
            std::cout << PROMPT << "You may not use that nickname. It's already in use." << std::endl;
            m_is_registered = false;
        }

    }
    else
    {
        std::cout << PROMPT << "Exiting, server offline." << std::endl;
        exit(0);
    }


}



// Registers the client with the server.
// This starts the registration process of the client contacting the server to register
void ChatClient::register_client()
{
    // We need only need to send our nickname and our online status to the server.
    // Our socket information already specifies our IP and port number, so that doesn't need to be part of the data packet.
}

//void ChatClient::deregister_client();

// Parses the client packet table in order to update the client's copy of the database
void ChatClient::parse_client_table_packet()
{
    // Packet Format: 
    // Number of clients as unsigned 32-bit int starting at 8th byte.
    // Starting on byte 12, we have sockaddr_in for the IP and Port
    // Followed by 1 character for the online status
    // Followed by a variable number of bytes of the client nickname
    
    // Now we need to read the data.
    // index 8, 9, 10, 11 contains the number of clients in the message
    
    uint32_t num_clients = 0;
    int ci = 8; // Current index into the m_socket_recv_buffer
    memcpy((void* ) &num_clients, (const void *) &(m_socket_recv_buffer[ci]), sizeof(num_clients)); // Copy 4 bytes
    num_clients = ntohl(num_clients);
    ci = ci + sizeof(num_clients);

    std::cout << "We recieved " << num_clients << " client(s) from the server." << std::endl;

    for(uint32_t entry = 0; entry < num_clients; entry++)
    {
        memcpy((void*)&(m_client_ip_port.sin_addr), (const void *)&( m_socket_recv_buffer[ci] ), sizeof(m_client_ip_port.sin_addr));
        ci = ci + sizeof(m_client_ip_port.sin_addr);
        memcpy((void*)&(m_client_ip_port.sin_port), (const void *)&( m_socket_recv_buffer[ci] ), sizeof(m_client_ip_port.sin_port));
        ci = ci + sizeof(m_client_ip_port.sin_port);
        bool status = ('1' == m_socket_recv_buffer[ci]); // Now holds the online status
        int client_status = (status) ? CLIENT_ONLINE : CLIENT_OFFLINE;
        ci = ci + 1;
        std::string tmp_nick(&(m_socket_recv_buffer[ci])); // Will construct the string and stop at the null character
        ci = ci + tmp_nick.size() + 1; // Need to skip over the \0 character too to the next entry
        
        update_client_table(tmp_nick, client_status, m_client_ip_port);
    }
}

// CLIENT based data processing
void ChatClient::parse_data_client()
{
    // Here we parse the recv buffer to see what type of data was recieved.
    // We expet the first few bytes to explicitly identify the application:
    // UDPCHAT should be the first 7 bytes.
    //
    // The 8th byte should indicate the packet type
    // 1) registration paket
    // 2) de-registration packet
    // 3) off-line chat packet
    //
    // let 1 = registration packet
    // let 2 = de-registration packet
    // let 3 = off-line chat request
    //
    // Registration packet 
    // -- the IP Address and Port number were handled by the other layer,
    // so they aren't needed here.
    // After the 8th byte, will be the nickname until the end of the datagram
    if(m_socket_recv_buffer_size <= 7)
    {
    }
    else
    {
        bool checkType = validate_recv_packet_header();
        if(checkType)
        {
            // Correct header found.
            char type = m_socket_recv_buffer[7];

            if('A'==type)
            {
                // Recieved an ACK
                std::cout << PROMPT << "Recieved an ACK" << std::endl;
            }
            else if('B'== type)
            {
                parse_client_table_packet();
                print_client_table();
            }
            else if ('M' == type) // Display the message
            {
                // UDPCHATM[clientname]\0[message]\0
                std::string recv_from_client(&(m_socket_recv_buffer[8])); // Reads the client name
                int idx = 8 + recv_from_client.size() + 1;
                std::string message(&(m_socket_recv_buffer[idx]));
                std::cout << recv_from_client << " says: " << message << std::endl;
                // After recieving the message, need to send an ACK
                send_ack(m_remote_ip_port);
            }
            else if('P' == type) // Poke request
            {
                std::cout << PROMPT << "Recieved a poke, need to send an ACK" << std::endl;
                send_ack(m_remote_ip_port);
            }
            else if ('E' == type) // Error message
            {
                std::string whom(&(m_socket_recv_buffer[8]));
                std::cout << PROMPT << "Status of " << whom << " is online." << std::endl;
            }
            else if ('O' == type) // You are recieving an offline message
            {
                std::string recv_from_client(&(m_socket_recv_buffer[8])); // Reads the client name
                send_ack(m_remote_ip_port);
                int idx = 8 + recv_from_client.size() + 1;
                std::string message(&(m_socket_recv_buffer[idx]));
                std::cout << recv_from_client << message << std::endl;
                // After recieving the message, need to send an ACK
            }
            else if('Z' == type)
            {
                // Indicate offline messages will be recieved
                std::cout << PROMPT << "[You have messages.]" << std::endl;
            }
        }
        else
        {
            // Invalid packet
            std::cout << PROMPT << "The server recieved an invalid packet!" << std::endl;
        }
    }


}































